﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.ComponentModel;

namespace GameEditor.Common.Controls
{
    public abstract class TypeBuilder
    {
        public virtual System.Collections.IList GetContainer(Component component)
        {
            return null;
        }

        public virtual void AddChild(Component component, Component child)
        {
            System.Collections.IList container = GetContainer(component);
            if (container != null)
                container.Add(child);
        }


        public abstract void SetName(Component component, String name);
        public abstract string GetName(Component child);


        public virtual Type GetAliasedType(String aliasName)
        {
            return null;
        }

        
    }


    class ControlTypeBuilder : TypeBuilder
    {
        public override void SetName(Component component, string name)
        {
            Control c = (component as Control);
            c.Text = name;
            c.Name = name;
        }

        public override string GetName(Component child)
        {
            return (child as Control).Name;
        }


        public override void AddChild(Component component, Component child)
        {
            if (child is ContextMenuStrip)
            {
                Control c = component as Control;
                c.ContextMenuStrip = child as ContextMenuStrip;
                return;
            }

            base.AddChild(component, child);
        }
        public override System.Collections.IList GetContainer(Component component)
        {
            return ((Control)component).Controls;
        }
    }
    class ContainerTypeBuilder : ControlTypeBuilder
    {
    }

    class FormTypeBuilder : ContainerTypeBuilder
    {
    }


    abstract class MenuTypeBuilder : TypeBuilder
    {
        public override Type GetAliasedType(string aliasName)
        {
            if ("Item".Equals(aliasName))
                return typeof(ToolStripMenuItem);
            return null;
        }
    }

    class MenuStripTypeBuilder : MenuTypeBuilder
    {
        public override System.Collections.IList GetContainer(Component component)
        {
            return (component as MenuStrip).Items;
        }

        public override string GetName(Component child)
        {
            return (child as MenuStrip).Name;
        }
        public override void SetName(Component component, string name)
        {
            (component as MenuStrip).Name = name;
        }
    }

    class MenuItemTypeBuilder : MenuTypeBuilder
    {

        public override System.Collections.IList GetContainer(Component component)
        {
            return (component as ToolStripMenuItem).DropDownItems;
        }

        public override string GetName(Component child)
        {
            return (child as ToolStripMenuItem).Name;
        }
        public override void SetName(Component component, string name)
        {
            ToolStripMenuItem item = component as ToolStripMenuItem;
            item.Text = name;
            item.Name = name;
        }
    }




    public class GUIBuilder
    {

        private static Dictionary<Type, TypeBuilder> typeBuilders = new Dictionary<Type, TypeBuilder>();
        private static Dictionary<Type, TypeBuilder> typeBuildersCache = new Dictionary<Type, TypeBuilder>();
        private Dictionary<String, Type> knownTypes = new Dictionary<string, Type>();


        static GUIBuilder()
        {
            typeBuilders[typeof(ContainerControl)] = new ContainerTypeBuilder();
            typeBuilders[typeof(MenuStrip)] = new MenuStripTypeBuilder();
            typeBuilders[typeof(ToolStripMenuItem)] = new MenuItemTypeBuilder();
            typeBuilders[typeof(Control)] = new ControlTypeBuilder();



        }

        /// <summary>
        /// 获得type到baseType继承关系距离，如果没有关系则返回-1
        /// </summary>
        /// <param name="type"></param>
        /// <param name="baseType"></param>
        /// <returns></returns>
        public static int GetInheritanceDepth(Type type, Type baseType)
        {
            int n = 0;
            Type p = type;
            while (p != null && p != baseType)
            {
                n++;
                p = p.BaseType;
            }
            if (p == null)
                return -1;
            return n;
        }
        /// <summary>
        /// 查找与type类型最接近的ITypeBuilder
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static TypeBuilder GetTypeBuilder(Type type)
        {
            if (typeBuildersCache.ContainsKey(type))
                return typeBuildersCache[type];
            int minDepth = 0;
            TypeBuilder best = null;
            foreach (KeyValuePair<Type, TypeBuilder> kv in typeBuilders)
            {
                int depth = GetInheritanceDepth(type, kv.Key);
                if (depth == -1)
                    continue;
                if (best == null)
                {
                    best = kv.Value;
                    minDepth = depth;
                }
                if (depth > minDepth)
                    continue;
                minDepth = depth;
                best = kv.Value;
            }
            if (best == null)
                return null;
            typeBuildersCache.Add(type, best);
            return best;
        }

        public ICollection<Type> GetKnownTypes()
        {
            return knownTypes.Values;
        }

        public void Import(String fullQualifiedName)
        {
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                Type type = assembly.GetType(fullQualifiedName, false, false);
                if (type != null)
                {
                    Import(type);
                    break;
                }
            }
        }
        public void Import(Type type)
        {
            if (!knownTypes.ContainsKey(type.Name))
                knownTypes.Add(type.Name, type);
        }

        public void Import<T>() where T : Component
        {
            Import(typeof(T));
        }

        public object EventTarget { get; set; }


        public GUIBuilder()
        {
            Import<Form>();
            Import<Button>();
            Import<CheckBox>();
            Import<TextBox>();
            Import<RadioButton>();
            Import<RichTextBox>();
            Import<Label>();
            Import<LinkLabel>();
            Import<ListBox>();
            Import<ComboBox>();
            Import<TreeView>();
            Import<ListView>();
            Import<MenuStrip>();
            Import<ToolStripMenuItem>();
            Import<ContextMenuStrip>();
            Import<PropertyGrid>();

            Import<NumericUpDown>();
            Import<ProgressBar>();
            Import<GroupBox>();
            Import<Panel>();
            Import<TabControl>();
            Import<Splitter>();
            Import<MonthCalendar>();
            Import<DateTimePicker>();

            Import<WebBrowser>();
            Import<ToolStrip>();
            Import<StatusStrip>();





        }
        public void SetProperty(Component comp, string name, string value)
        {
            Type componentType = comp.GetType(); 

            PropertyInfo pi = componentType.GetProperty(name);
            if (pi != null)
            {
                SetPropertyImpl(comp, value, pi);
                return;
            }
            EventInfo ei = componentType.GetEvent(name);
            if (ei != null)
            {
                SetEventImpl(comp, value, ei);
            }

        }

        class EventDelegate
        {
            public static readonly MethodInfo EventMethod = typeof(EventDelegate).GetMethod("OnEvent");


            public object Target { get; set; }
            public MethodInfo Callback { get; set; }

            public void OnEvent(Object sender, EventArgs e)
            {
                Callback.Invoke(Target, new object[] { });
            }
        }

        private void SetEventImpl(Component comp, string value, EventInfo ei)
        {
            if (EventTarget == null)
                return;
            Type listenerType = EventTarget.GetType();
            MethodInfo callback = listenerType.GetMethod(value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            //如果回调没有参数，而目标类型是一个EventHandler，则创建一个事件代理
            if (callback.GetParameters().Length == 0 && ei.EventHandlerType == typeof(EventHandler))
            {
                EventDelegate d = new EventDelegate { Target = EventTarget, Callback = callback };
                System.Delegate delegateCallback = Delegate.CreateDelegate(typeof(EventHandler), d, EventDelegate.EventMethod);
                ei.AddEventHandler(comp, delegateCallback);
                return;
            }
            else
            {
                System.Delegate handler = Delegate.CreateDelegate(ei.EventHandlerType, EventTarget, callback);
                //否则直接关联上去
                ei.AddEventHandler(comp, handler);
            }

        }

        private static void SetPropertyImpl(Component comp, string value, PropertyInfo pi)
        {

            Type propertyType = pi.PropertyType;
            //检查是否是枚举类型
            if (propertyType.IsEnum)
            {
                Int32 flags = 0;

                foreach (string str in value.Split(',', '+', '|'))
                {
                    FieldInfo field = propertyType.GetField(str.Trim());
                    if (field != null)
                    {
                        int flag = (field.GetValue(null) as IConvertible).ToInt32(null);
                        flags |= flag;
                    }
                }
                pi.SetValue(comp, flags, null);
                return;
            }


            TypeConverter converter = TypeDescriptor.GetConverter(propertyType);


            if (converter != null)
            {
                object v = converter.ConvertFromString(value);
                pi.SetValue(comp, v, null);
                return;
            }


            pi.SetValue(comp, value, null);
        }


        public Type GetType(String typeName)
        {
            if (knownTypes.ContainsKey(typeName))
                return knownTypes[typeName];
            return Type.GetType(typeName);
        }

        public Component CreateObject(Component parent, string typeName, string name)
        {
            TypeBuilder parentBuilder = null;
            if (parent != null)
                parentBuilder = GetTypeBuilder(parent.GetType());
            Type type = null;
            if (parentBuilder != null)
                type = parentBuilder.GetAliasedType(typeName);
            if(type==null)
                type = GetType(typeName);
                
            if (type == null)
                return null;

            TypeBuilder childBuilder = GetTypeBuilder(type);
            Component ret = Activator.CreateInstance(type) as Component;

            if (!string.IsNullOrEmpty(name))
                childBuilder.SetName(ret, name);
            if (parentBuilder != null)
                parentBuilder.AddChild(parent, ret);
            return ret;
        }
    }



    /**
     * 
     *  TYPE_NAME   ::= IDENTITY(.IDENTITY)*;
     *  NAME        ::= IDENTITY
     *  VALUE       ::= STRING | IDENTITY
     *  PARAM       ::= IDENTITY = VALUE
     *  PARAM_LIST  ::= { (PARAM|OBJECT)* }
     *  IMPORT      ::= import TYPE_NAME;
     *  FILE        ::= IMPORT* OBJECT*
     *  OBJECT      ::= TYPE_NAME NAME? PARAM_LIST?
     * 
     * 
     */
    public class GUIParser
    {
        private GUIBuilder builder;
        private GUITokenlizer tokenlizer;

        public GUIParser(GUIBuilder builder)
        {
            this.builder = builder;
        }

        public Component[] Parse(String code)
        {
            tokenlizer = new GUITokenlizer(code.ToCharArray());
            return readSource();
        }


        private GUITokenlizer.Token Match(GUITokenlizer.TokenType expected, bool canConsume, bool allowEOF)
        {
            GUITokenlizer.Token token = tokenlizer.Next();

            if (allowEOF && token.Type == GUITokenlizer.TokenType.EOF)
                return token;

            if (token.Type == expected)
            {
                if (!canConsume)
                    tokenlizer.Unget(token);
                return token;
            }
            String message = String.Format("期望一个{0}，但找到{1}", GUITokenlizer.TokenTypeToString(expected), token.Data);
            tokenlizer.RaiseException<UnexpectedTokenException>(token.Cursor, message);
            return token;
        }
        private GUITokenlizer.Token Match(GUITokenlizer.TokenType[] expected, bool canConsume, bool allowEOF)
        {
            GUITokenlizer.Token token = tokenlizer.Next();

            if (allowEOF && token.Type == GUITokenlizer.TokenType.EOF)
                return token;

            if (expected.Contains(token.Type))
            {
                if (!canConsume)
                    tokenlizer.Unget(token);
                return token;
            }
            StringBuilder sb = new StringBuilder();
            foreach (GUITokenlizer.TokenType type in expected)
            {
                if (sb.Length != 0)
                    sb.Append("或");
                sb.Append(GUITokenlizer.TokenTypeToString(type));
            }
            String message = String.Format("期望一个{0}，但找到{1}", sb, token.Data);
            tokenlizer.RaiseException<UnexpectedTokenException>(token.Cursor, message);
            return token;
        }
        private GUITokenlizer.Token Predicate(GUITokenlizer.TokenType expected)
        {
            GUITokenlizer.Token token = tokenlizer.Next();
            if (token.Type == expected)
                return token;
            //not expected, return to the tokenlizer
            tokenlizer.Unget(token);
            return null;
        }
        private GUITokenlizer.Token Predicate(GUITokenlizer.TokenType[] expected)
        {
            GUITokenlizer.Token token = tokenlizer.Next();
            if (expected.Contains(token.Type))
                return token;
            //not expected, return to the tokenlizer
            tokenlizer.Unget(token);
            return null;
        }

        private Component[] readSource()
        {
            GUITokenlizer.Token token;
            List<Component> components = new List<Component>();
            while ((token = Match(GUITokenlizer.TokenType.Identity, false, true)).Type != GUITokenlizer.TokenType.EOF)
            {
                if ("import".Equals(token.Data))
                {
                    readImport();
                    continue;
                }
                Component comp = readObject(null);
                components.Add(comp);
            }
            return components.ToArray();
        }
        private Component readObject(Component parent)
        {
            //OBJECT      ::= TYPE_NAME NAME? PARAM_LIST?
            String objectType = null;
            String objectName = null;
            //TYPE_NAME
            GUITokenlizer.Token token = Match(GUITokenlizer.TokenType.Identity, false, false);
            int cursor = token.Cursor;
            objectType = readTypeName();
            //NAME
            token = Predicate(new GUITokenlizer.TokenType[] { GUITokenlizer.TokenType.Identity, GUITokenlizer.TokenType.String });
            if (token != null)
                objectName = token.Data;

            //create component
            Component comp = null;
            comp = builder.CreateObject(parent, objectType, objectName);
            if (comp == null)
                tokenlizer.RaiseException<ObjectCreationException>(cursor, "对象创建失败：" + objectType);


            //PARAM_LIST
            token = Predicate(GUITokenlizer.TokenType.BlockStart);
            if (token != null)
            {
                while (true)
                {
                    token = Predicate(GUITokenlizer.TokenType.BlockEnd);
                    if (token != null)
                        break;

                    token = Match(GUITokenlizer.TokenType.Identity, true, false);
                    cursor = token.Cursor;
                    //parse property assignment
                    if (Predicate(GUITokenlizer.TokenType.Equal) != null)
                    {
                        String propName = token.Data;
                        String propValue = readValue();

                        //assign to object
                        try
                        {
                            builder.SetProperty(comp, propName, propValue);
                        }
                        catch (Exception e)
                        {
                            tokenlizer.RaiseException<PropertyAssignmentException>(cursor, "属性更新失败：" + e.Message);
                        }


                        Match(GUITokenlizer.TokenType.Semicolon, true, false);
                        continue;
                    }
                    tokenlizer.Unget(token);
                    //read child object definition
                    readObject(comp);
                }
            }
            else
            {
                //没有{}的完整定义，则需要以;结尾，这点和C-like语言一致
                Match(GUITokenlizer.TokenType.Semicolon, true, false);
            }
            //
            return comp;
        }



        private string readValue()
        {
            GUITokenlizer.Token token = Match(new GUITokenlizer.TokenType[] { GUITokenlizer.TokenType.Identity, GUITokenlizer.TokenType.String, GUITokenlizer.TokenType.Number }, true, false);
            return token.Data;
        }
        private void readImport()
        {
            GUITokenlizer.Token token = Match(GUITokenlizer.TokenType.Identity, true, false);
            Match(GUITokenlizer.TokenType.Identity, false, false);
            String typeName = readTypeName();
            Match(GUITokenlizer.TokenType.Semicolon, true, false);
            
            try
            {
                builder.Import(typeName);
            }
            catch (Exception e)
            {
                tokenlizer.RaiseException<ImportException>(token.Cursor, "导入类型失败：" + e.Message);

            }
        }
        private string readTypeName()
        {
            StringBuilder sb = new StringBuilder();
            GUITokenlizer.TokenType[] types = new GUITokenlizer.TokenType[] { GUITokenlizer.TokenType.Dot, GUITokenlizer.TokenType.Identity };
            GUITokenlizer.Token token = null;
            GUITokenlizer.TokenType lastType = GUITokenlizer.TokenType.EOF;
            while ((token = Predicate(types)) != null && lastType != token.Type)
            {
                sb.Append(token.Data);
                lastType = token.Type;
            }
            if (token != null)
                tokenlizer.Unget(token);
            if (sb.Length == 0)
                return null;
            return sb.ToString();
        }

    }

    public class GUITokenlizer
    {
        private char[] data;
        private int cursor;
        private int lastCursor;
        private int line = 1;
        private int column = 2;

        private Stack<Token> tokens = new Stack<Token>();

        public enum TokenType
        {
            Identity,
            String,
            Number,
            Semicolon,
            Dot,
            Equal,
            BlockStart,
            BlockEnd,
            EOF
        }
        public class Token
        {
            public String Data { get; set; }
            public TokenType Type { get; set; }
            public int Cursor { get; set; }
            public int Line { get; set; }
            public int Column { get; set; }
            public Token()
            {
            }
            public Token(int cursor, String data, TokenType type)
            {
                this.Cursor = cursor;
                this.Data = data;
                this.Type = type;
            }
        }

        public static String TokenTypeToString(TokenType type)
        {
            switch (type)
            {
                case TokenType.BlockStart:
                    return "{";
                case TokenType.BlockEnd:
                    return "}";
                case TokenType.Dot:
                    return ".";
                case TokenType.EOF:
                    return "文件结尾";
                case TokenType.Equal:
                    return "=";
                case TokenType.Identity:
                    return "标识符";
                case TokenType.Number:
                    return "数字";
                case TokenType.Semicolon:
                    return ";";
                case TokenType.String:
                    return "字符串";
            }
            return "<未知>";
        }

        public GUITokenlizer(char[] data)
        {
            this.data = data;
            this.cursor = 0;
        }


        private void SkipBlanks()
        {
            while (cursor < data.Length && char.IsWhiteSpace(data[cursor]))
            {
                cursor++;
            }
        }
        private char Peek()
        {
            return data[cursor];
        }
        private char Get()
        {
            char c = data[cursor];
            cursor++;
            if (cursor > lastCursor)
            {
                if (c == '\n')
                {
                    line++;
                    column = 1;
                }
                else
                {
                    column++;
                }
                lastCursor = cursor;
            }
            return c;
        }
        private void Unget()
        {
            cursor--;
        }
        private void Advance()
        {
            cursor++;
        }
        public bool IsEOF
        {
            get
            {
                return cursor >= data.Length;
            }

        }

        public void Unget(Token token)
        {
            tokens.Push(token);
        }

        public Token Next()
        {
            if (tokens.Count != 0)
            {
                return tokens.Pop();
            }
            SkipBlanks();

            int line = this.line;
            int column = this.column;
            Token ret = NextImpl();
            ret.Line = line;
            ret.Column = column;
            return ret;
        }

        private Token NextImpl()
        {


            if (cursor >= data.Length)
                return new Token { Data = "", Type = TokenType.EOF };
            int oldCursor = cursor;
            char c = Get();
            switch (c)
            {
                case '{':
                    return new Token(oldCursor, "{", TokenType.BlockStart);
                case '}':
                    return new Token(oldCursor, "}", TokenType.BlockEnd);
                case '.':
                    return new Token(oldCursor, ".", TokenType.Dot);
                case '=':
                    return new Token(oldCursor, "=", TokenType.Equal);
                case ';':
                    return new Token(oldCursor, ";", TokenType.Semicolon);
                case '(':
                case '\'':
                case '\"':
                    {
                        char endQuot = c;
                        if (c == '(')
                            endQuot = ')';
                        return ReadString(oldCursor, endQuot);
                    }
                case '$':
                    return ReadHex(oldCursor);
                default:
                    Unget();
                    if (char.IsDigit(c))
                        return ReadNumber(oldCursor);
                    if (!char.IsLetter(c) && c != '_')
                        RaiseException<InvalidTokenException>(oldCursor, "未知的字符 " + c);
                    return ReadIdentity(oldCursor);
            }

        }

        private Token ReadHex(int oldCursor)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("0x");
            while (!IsEOF)
            {
                char c = Get();
                if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))
                {
                    sb.Append(c);
                    continue;
                }
                Unget();
                break;
            }
            return new Token(oldCursor, sb.ToString(), TokenType.Number);
        }
        private Token ReadNumber(int oldCursor)
        {
            StringBuilder sb = new StringBuilder();
            bool isFloat = false;
            while (!IsEOF)
            {
                char c = Get();
                if ((c >= '0' && c <= '9') || (!isFloat && c == '.'))
                {
                    sb.Append(c);
                    if (c == '.')
                        isFloat = true;
                    continue;
                }
                Unget();
                break;
            }
            return new Token(oldCursor, sb.ToString(), TokenType.Number);

        }

        private Token ReadIdentity(int oldCursor)
        {
            StringBuilder sb = new StringBuilder();
            while (!IsEOF)
            {
                char c = Get();
                if (char.IsLetterOrDigit(c) || c == '_')
                {
                    sb.Append(c);
                    continue;
                }
                Unget();
                break;
            }
            return new Token(oldCursor, sb.ToString(), TokenType.Identity);
        }

        private Token ReadString(int oldCursor, char quot)
        {
            StringBuilder sb = new StringBuilder();
            while (true)
            {
                if (IsEOF)
                    RaiseException<NewLineInConstantException>(oldCursor, "字符串常量中包含了换行符！");
                char c = Get();

                if (c == quot)
                    break;
                sb.Append(c);
            }
            return new Token(oldCursor, sb.ToString(), TokenType.String);
        }

        public void RaiseException<T>(int cursor, string message) where T : Exception
        {
            int line = 0;
            int column = 0;
            GetLineColumn(cursor, out line, out column);
            T exception = (T)Activator.CreateInstance(typeof(T), new object[] { line, column, message });
            throw exception;
        }
         
        public void GetLineColumn(int cursor, out int line, out int column)
        {
            line = 1;
            column = 1;
            for (int i = 0; i < cursor; i++)
            {
                if (data[i] == '\n')
                {
                    line++;
                    column = 1;
                }
                else
                {
                    column++;
                }
            }

        }



    }

    public class SyntaxException : Exception
    {
        public int ScriptLine { get; set; }
        public int ScriptColumn { get; set; }

        public SyntaxException(int line, int column, String exception)
            : base(exception)
        {
            ScriptLine = line;
            ScriptColumn = column;
        }

    }

    public class InvalidTokenException : SyntaxException
    {
        public InvalidTokenException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }
    public class NewLineInConstantException : SyntaxException
    {
        public NewLineInConstantException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }
    public class UnexpectedTokenException : SyntaxException
    {
        public UnexpectedTokenException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }
    public class ObjectCreationException : SyntaxException
    {
        public ObjectCreationException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }
    public class PropertyAssignmentException : SyntaxException
    {
        public PropertyAssignmentException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }
    public class ImportException : SyntaxException
    {
        public ImportException(int line, int column, String exception)
            : base(line, column, exception)
        {
        }
    }

}
